/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package zombiedefense.sprites;

import zombiedefense.map.Raycaster;
import zombiedefense.util.MathEx;
import zombiedefense.util.Timer;
import zombiedefense.map.TileMap;
import java.util.Random;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Point2D;
import static zombiedefense.util.MathEx.loadImageFromJar;

/**
 * Defines a player.
 * The player can hold a gun and
 * has several fields that are used in gameplay.
 * @author Laptop2010
 */
public class Player extends Sprite implements Cloneable {

    private int score = 0, money = 0;
    private Gun currentgun;
    int playerScreenX, playerScreenY;
    private static final Point pistolHoldPoint = new Point(42, 5);
    private static final Point rifleHoldPoint = new Point(45, 22);
    private Point2D.Double realCenterPoint = new Point2D.Double();
    private Point2D.Double realHoldPoint = new Point2D.Double();
    private Point2D.Double realGunPivotPoint = new Point2D.Double();
    private Point2D.Double realGunBarrelPoint = new Point2D.Double();
    private static final Point2D.Double bulletimpact = new Point2D.Double();
    private static final int playerZ = 0;
    private static final int stillPistolAnim = 0;
    private static final int walkingPistolAnim = 1;
    private static final int stillRifleAnim = 2;
    private static final int walkingRifleAnim = 3;
    private Timer guntimer = new Timer();

    private enum GunState {
	READY,FIRING,RELOADING,WAITING;
    };
    private GunState gState;
    private int killCount = 0;
    private int killCountCurrentWave = 0;

    public Player(double x, double y, double health, int cash) {
	super(getAnims(), stillPistolAnim, x, y, playerZ, health);

	//store starting money
	money = cash;

	gState = GunState.READY;
    }

    public void setGun(Gun g) {
	currentgun = g;
    }

    public int getCash() {
	return money;
    }

    public void giveCash(int dollars) {
	money += dollars;
    }

    public void takeCash(int dollars) {
	money -= dollars;
	if (money < 0) {
	    money = 0;
	}
    }

    public void nextGun() {
	Gun[] guns = Gun.values();
	do {
	    if (currentgun.ordinal() == guns.length - 1) {
		currentgun = guns[0];
	    } else {
		currentgun = guns[currentgun.ordinal() + 1];
	    }
	    //loop until current gun is not locked
	} while (currentgun.isLocked());
    }

    public void previousGun() {
	Gun[] guns = Gun.values();
	do {
	    if (currentgun.ordinal() == 0) {
		currentgun = guns[guns.length - 1];
	    } else {
		currentgun = guns[currentgun.ordinal() - 1];
	    }
	} while (currentgun.isLocked());
    }

    public void update(long elapsedTime) {
	super.update(elapsedTime);

	//update timer
	guntimer.update(elapsedTime);

	switch (gState) {
	    //gun is ready to perform some action
	    case READY:
		//nothing to do
		break;
	    //fire event - executed once per shot
	    case FIRING:

		//sound & stats
		if (currentgun.fire()) {
		    //visual
		    effects.addGunFlash(currentgun.getFlash(),
			    realGunBarrelPoint, rotation);

		    //raycast
		    Point2D.Double barrel = realGunBarrelPoint;
		    double dir;
		    byte type;

		    for (int i = 0; i < currentgun.getNumbulletsfired(); i++) {
			dir = getBulletDirection();

			//find hits with wall
			type = rayCaster.raycast(this, bulletimpact, barrel.x, barrel.y, dir, (byte) (Raycaster.DETECT_WALLS_MASK | Raycaster.DETECT_ZOMBIES_MASK));

			switch (type) {
			    case Raycaster.DETECT_WALLS_MASK:
				//show impact point
				effects.addSpark(bulletimpact, dir);
				break;
			    case Raycaster.DETECT_ZOMBIES_MASK:
				//show bloody impact
				effects.addBloodSpurt(bulletimpact);
				break;
			}

			//show tracer
			effects.addTracer(bulletimpact, barrel);
		    }
		    
		    //change state
		    gState = GunState.WAITING;
		    
		    //set up the wait - for firing sound
		    guntimer.setInterval(currentgun.getFiringinterval());
		}else{
		    //could not fire for some reason
		    gState = GunState.WAITING;
		    
		    //set up wait - for dry fire sound
		    guntimer.setInterval(currentgun.getDryFiringInterval());
		}
		
		break;
	    //reload event - executed once per reload
	    case RELOADING:
                //manipulate data and play sound
		currentgun.reload();
                
                //start waiting for sound to finish
                guntimer.setInterval(currentgun.getReloadInterval());
                
                gState = GunState.WAITING;
		
		break;
            case WAITING:
                //gun is disabled while we wait for action to finish
                if(!guntimer.isStarted()){
                    guntimer.reset();
                    guntimer.start();
                }
                
                if(guntimer.hasTicked()){
                    guntimer.stop();
                    guntimer.reset();
                    gState = GunState.READY;
                }
	}

	if (velocity.x != 0 || velocity.y != 0) {
	    setAnimation(anims[(currentgun.getType() == Gun.type_pistol) ? walkingPistolAnim : walkingRifleAnim]);
	} else {
	    setAnimation(anims[(currentgun.getType() == Gun.type_pistol) ? stillPistolAnim : stillRifleAnim]);
	}
    }

    public void draw(Graphics2D g, int drawx, int drawy, TileMap map) {
	super.draw(g, drawx, drawy, map);  //will draw gun after player body

	//draw gun
	MathEx.drawRotatedImage(g, currentgun.getImage(), rotation + MathEx.halfPi,
		map.getScreenX(realGunPivotPoint.x - TileMap.pixelsToGridUnits(currentgun.getPivot().x)),
		map.getScreenY(realGunPivotPoint.y - TileMap.pixelsToGridUnits(currentgun.getPivot().y)),
		map.getScreenX(realGunPivotPoint.x), map.getScreenY(realGunPivotPoint.y));
    }

    /**
     * Rotates points associated with the player with respect to the current rotation
     */
    public void rotatePoints() {
	//setup center point
	realCenterPoint.setLocation(centerX(), centerY());

	//setup gun hold point
	Point holdpoint = (currentgun.getType() == Gun.type_pistol) ? pistolHoldPoint : rifleHoldPoint;
	realHoldPoint.setLocation(getX() + TileMap.pixelsToGridUnits(holdpoint.x),
		getY() + TileMap.pixelsToGridUnits(holdpoint.y));

	//rotate the point
	MathEx.rotatePoint(realHoldPoint, realCenterPoint, rotation + Math.toRadians(90));

	//setup pivot point
	realGunPivotPoint.setLocation(realHoldPoint);

	//setup barrel point
	realGunBarrelPoint.setLocation(realGunPivotPoint.x, realGunPivotPoint.y
		- (TileMap.pixelsToGridUnits(currentgun.getPivot().y) - TileMap.pixelsToGridUnits(currentgun.getBarrel().y)));

	//rotate barrel point
	MathEx.rotatePoint(realGunBarrelPoint, realGunPivotPoint, rotation + Math.toRadians(90));

	//end of point rotation
    }

    /**
     * Takes care of the player's visual and audio shooting effect.
     * @return 
     */
    public void shoot() {
	//register a fire event
	if (gState == GunState.READY) {
	    //if the gun is not ready, fire event is ignored
	    gState = GunState.FIRING;
	}
    }

    public void reload() {
	//change the state
	if (gState == GunState.READY) {
	    //ignore events if gun isn't ready to reload
	    gState = GunState.RELOADING;
	}
    }

    /**
     * Returns the player's gun.
     * @return 
     */
    public Gun getGun() {
	return currentgun;
    }

    /**
     * Factors in the accuracy of the player's gun to give the actual
     * direction of a bullet.
     * @return 
     */
    private double getBulletDirection() {
	Random r = MathEx.getRandom();
	return rotation + r.nextDouble()
		* currentgun.getAccuracy()
		* ((r.nextBoolean()) ? 1 : -1);
    }

    public double getHealthRatio() {
	return health / startHealth;
    }

    public Point2D.Double getBarrelPoint() {
	return realGunBarrelPoint;
    }

    public Object clone() {
	try {
	    return super.clone();
	} catch (Exception e) {
	    e.printStackTrace();
	}

	return null;
    }

    public void collision(Sprite other) {
	if (other instanceof Zombie) {
	    hit(((Zombie) other).getType().getPower());
	}
    }

    public void rayHit(Sprite source) {
    }

    /**
     * React to kills.
     * @param z 
     */
    public void notifyKilled(Zombie z) {
	//add money
	//giveCash(z.getType().getReward());
	//add points
	//givePoints(z.getType().getPoints());
	//add to killCount
	killCountCurrentWave++;
    }

    public void notifyWaveAdvanced() {
	killCount += killCountCurrentWave;
	killCountCurrentWave = 0;
    }

    public int getKillCount() {
	return killCount;
    }

    public int getKillCountCurrentWave() {
	return killCountCurrentWave;
    }

    protected static Animation[] getAnims() {
	Animation[] anims = {
	    new Animation(), //still pistol
	    new Animation(), //walking pistol
	    new Animation(), //still rifle
	    new Animation() //walking rifle
	};

	anims[stillPistolAnim].addFrame(loadImageFromJar("/images/hero1.png"), 0);

	anims[walkingPistolAnim].addFrame(loadImageFromJar("/images/hero2.png"), 200);
	anims[walkingPistolAnim].addFrame(loadImageFromJar("/images/hero3.png"), 200);

	anims[stillRifleAnim].addFrame(loadImageFromJar("/images/hero4.png"), 0);

	anims[walkingRifleAnim].addFrame(loadImageFromJar("/images/hero5.png"), 200);
	anims[walkingRifleAnim].addFrame(loadImageFromJar("/images/hero6.png"), 200);

	return anims;
    }
}
